package com.leo.tool.rsa;

import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.Cipher;

/**
 * RSA加密
 * 使用Base64编码
 * 
 * @author Leo
 * @date 2019/08/12
 */
public class RSAUtil {

    // 数字签名，秘钥算法
    public static final String KEY_ALGORITHM = "RSA";

    // 数字签名，签名/验证算法
    public static final String STGNATURE_ALGORITHM = "MD5withRSA";

    // 私钥长度，默认是1024,秘钥长度必须是64的整数倍。范围在512~65536之间
    private static int KEY_SIZE = 1024;

    // 公钥
    public static final String PUBLIC_KEY = "RSAPublicKey";

    // 私钥
    public static final String PRIVATE_KEY = "RSAPrivateKey";

    public static void main(String[] args) throws Exception {
        Map<String, String> keys = generateKeyMap();
        String base64PublicKey = keys.get(PUBLIC_KEY);
        String base64privateKey = keys.get(PRIVATE_KEY);
        RSAPublicKey loadPublicKey = loadPublicKey(base64PublicKey);
        RSAPrivateKey loadPrivateKey = loadPrivateKey(base64privateKey);

        String content = "hello world!";

        // 加解密
        byte[] encryptedData = encrypt(loadPublicKey, content.getBytes());
        String decryptData = new String(decrypt(loadPrivateKey, encryptedData));
        System.out.println(content.equals(decryptData));

        // 签名&验签
        byte[] sign = sign(loadPrivateKey, content.getBytes());
        boolean result = verify(loadPublicKey, content.getBytes(), sign);
        System.out.println(result);
    }

    public static KeyPair generateKeyPair() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(KEY_SIZE, new SecureRandom());
        return keyPairGen.generateKeyPair();
    }

    public static Map<String, String> generateKeyMap() throws Exception {
        KeyPair keyPair = generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey)keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey)keyPair.getPrivate();
        Map<String, String> keyMap = new HashMap<>(2);
        keyMap.put(PUBLIC_KEY, Base64.getEncoder().encodeToString(publicKey.getEncoded()));
        keyMap.put(PRIVATE_KEY, Base64.getEncoder().encodeToString(privateKey.getEncoded()));
        return keyMap;
    }

    public static RSAPublicKey loadPublicKey(String base64PublicKey) throws Exception {
        byte[] buffer = Base64.getDecoder().decode(base64PublicKey);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
        return (RSAPublicKey)keyFactory.generatePublic(keySpec);
    }

    public static RSAPrivateKey loadPrivateKey(String base64privateKey) throws Exception {
        byte[] buffer = Base64.getDecoder().decode(base64privateKey);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        return (RSAPrivateKey)keyFactory.generatePrivate(keySpec);
    }

    public static byte[] encrypt(Key key, byte[] plainTextData) throws Exception {
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(plainTextData);
    }

    public static byte[] decrypt(Key key, byte[] cipherData) throws Exception {
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(cipherData);
    }

    public static byte[] sign(PrivateKey privateKey, byte[] data) throws Exception {
        Signature signature = Signature.getInstance(KEY_ALGORITHM);
        signature.initSign(privateKey);
        signature.update(data);
        return signature.sign();
    }

    public static boolean verify(PublicKey publicKey, byte[] data, byte[] sign) throws Exception {
        Signature signature = Signature.getInstance(KEY_ALGORITHM);
        signature.initVerify(publicKey);
        signature.update(data);
        return signature.verify(sign);
    }

}
